from typing import Callable

from fastapi import APIRouter, Request
from sqlalchemy import Table, orm, sql

from base.auth import authentication
from config.database import Session

from .schemas import *


class CRUD:
    model: Base
    router: APIRouter
    scope: str
    superadmin: bool = False

    @classmethod
    def prefix(cls):
        return f"/{cls.model.__name__.lower()}"

    @classmethod
    def description(cls):
        return (cls.model.__doc__ or "").strip().splitlines()[0]

    @classmethod
    def get_schema_model(cls, method: str, model: Base.__class__ | None = None):
        if model:
            name = model.__name__
        else:
            name = cls.model.__name__

        match method:
            case "gets":
                schema_name = f"{name}PaginationModel"
            case "get":
                schema_name = f"{name}Model"
            case "create":
                schema_name = f"{name}CreateModel"
            case "update":
                schema_name = f"{name}UpdateModel"
            case _:
                schema_name = None

        if schema_name:
            for schema in BaseModel.__subclasses__():
                if schema.__name__ == schema_name:
                    return schema

        return None

    @classmethod
    def get_relationship_columns(
        cls, relationship_model: Base.__class__, table: Table | Base.__class__
    ):
        column = None
        relationship_column = None

        if isinstance(table, Base.__class__):
            _table = table.__table__
        else:
            _table = table

        for c in _table.c:
            if c.foreign_keys:
                name = (list(c.foreign_keys)[0]).target_fullname.replace(".id", "")

                if name == cls.model.__table__.name:
                    if isinstance(table, Base.__class__):
                        column = getattr(table, c.name)
                    else:
                        column = c

                    continue

                if name == relationship_model.__table__.name:
                    if isinstance(table, Base.__class__):
                        relationship_column = getattr(table, c.name)
                    else:
                        relationship_column = c

                    continue

        return column, relationship_column

    @classmethod
    def get_relationship_attribute(
        cls, obj, relationship_model: Base.__class__, table: Table | Base.__class__
    ):
        for name in set(cls.model.__dict__.keys()) - set(cls.model.__table__.c.keys()):
            attribute = getattr(cls.model, name)

            if not isinstance(attribute, orm.InstrumentedAttribute):
                continue

            if isinstance(table, Base.__class__):
                if attribute.property.argument != table.__name__:
                    continue

                if attribute.property.secondary != None:
                    continue
            else:
                if attribute.property.argument != relationship_model.__name__:
                    continue

                if attribute.property.secondary != table:
                    continue

            return getattr(obj, attribute.property.key)

        return None

    @classmethod
    def initialize(cls):
        async def gets(
            request: Request, page: int = 1, limit: int = 10, kwargs: str = None
        ):
            """
            对象分页列表
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            input_model = PaginationArgsModel(page=page, limit=limit)
            response_model = PaginationModel()

            with Session() as session, session.begin():
                session.add(auth_user)

                stmt = sql.select(cls.model)

                config = cls.Config()

                if config.hook_gets_queryset:
                    stmt = config.hook_gets_queryset(
                        session, stmt, auth_user, request, kwargs
                    )

                response_model.init(session, stmt, input_model._limit())

                for obj in input_model.query(session, stmt).scalars():
                    schema = cls.get_schema_model("gets").from_obj(obj)
                    schema.update(obj)

                    if config.hook_gets_schema:
                        config.hook_gets_schema(session, obj, schema)

                    response_model.data.append(schema)

            return response_model

        async def get(request: Request, id: int):
            """
            获取对象信息
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = session.get(cls.model, id)

                if not obj:
                    raise HTTPException(
                        detail=f"未知{cls.description()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                config = cls.Config()

                if config.hook_get_check:
                    config.hook_get_check(obj)

                response_model = cls.get_schema_model("get").from_obj(obj)
                response_model.update(obj)

                if config.hook_get_schema:
                    config.hook_get_schema(session, obj, schema)

            return response_model

        async def create(request: Request, data: cls.get_schema_model("create")):
            """
            新建对象
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = cls.model()
                obj.from_data(data.dict_data(), session=session)

                config = cls.Config()

                if config.hook_create_obj:
                    config.hook_create_obj(session, obj, auth_user, data)

                if hasattr(obj, "validate"):
                    validate = getattr(obj, "validate")

                    if callable(validate):
                        validate(session=session)

                session.add(obj)

            return SuccessModel()

        async def update(
            request: Request, id: int, data: cls.get_schema_model("update")
        ):
            """
            更新对象
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = session.get(cls.model, id)

                if not obj:
                    raise HTTPException(
                        detail=f"未知{cls.description()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                config = cls.Config()

                if config.hook_update_check:
                    config.hook_update_check(obj)

                obj.from_data(data.dict_data())

                if config.hook_update_obj:
                    config.hook_update_obj(session, obj, auth_user, data)

                if hasattr(obj, "validate"):
                    validate = getattr(obj, "validate")

                    if callable(validate):
                        validate()

                session.add(obj)

            return SuccessModel()

        async def delete(request: Request, ids: list[int]):
            """
            删除对象
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                config = cls.Config()

                if config.hook_delete:
                    for obj in session.execute(
                        sql.select(cls.model).where(cls.model.id.in_(ids))
                    ).scalars():
                        config.hook_delete(session, obj)
                        session.delete(obj)
                else:
                    session.execute(sql.delete(cls.model).where(cls.model.id.in_(ids)))

            return SuccessModel()

        async def delete_one(request: Request, id: int):
            """
            删除对象
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                config = cls.Config()

                if config.hook_delete:
                    for obj in session.execute(
                        sql.select(cls.model).where(cls.model.id == id)
                    ).scalars():
                        config.hook_delete(session, obj)
                        session.delete(obj)
                else:
                    session.execute(sql.delete(cls.model).where(cls.model.id == id))

            return SuccessModel()

        def add_api_route():
            config = cls.Config()

            if config.gets:
                cls.router.add_api_route(
                    cls.prefix(),
                    endpoint=gets,
                    methods=["GET"],
                    response_model=PaginationModel,
                    summary=f"{cls.description()}分页列表",
                )

            if config.get:
                cls.router.add_api_route(
                    cls.prefix() + "/{id:int}",
                    endpoint=get,
                    methods=["GET"],
                    response_model=cls.get_schema_model("get"),
                    summary=f"{cls.description()}信息",
                )

            if config.create:
                cls.router.add_api_route(
                    cls.prefix(),
                    endpoint=create,
                    methods=["POST"],
                    response_model=SuccessModel,
                    summary=f"新建{cls.description()}",
                )

            if config.update:
                cls.router.add_api_route(
                    cls.prefix() + "/{id:int}",
                    endpoint=update,
                    methods=["PUT"],
                    response_model=SuccessModel,
                    summary=f"更新{cls.description()}",
                )

            if config.delete:
                cls.router.add_api_route(
                    cls.prefix(),
                    endpoint=delete,
                    methods=["DELETE"],
                    response_model=SuccessModel,
                    summary=f"删除{cls.description()}",
                )

            if config.delete_one:
                cls.router.add_api_route(
                    cls.prefix() + "/{id:int}",
                    endpoint=delete_one,
                    methods=["DELETE"],
                    response_model=SuccessModel,
                    summary=f"删除{cls.description()}",
                )

        add_api_route()

    @classmethod
    def initialize_choice(cls):
        async def choice(request: Request, name: str):
            """
            CHOICE列表
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            response_model = ListModel()

            with Session() as session, session.begin():
                session.add(auth_user)

                choices = None

                for c in cls.model.__table__.c:
                    if c.name.lower() != name.lower():
                        continue

                    if not isinstance(c.type, ChoiceType):
                        continue

                    choices = c.type.choices

                    break

                if not choices:
                    raise HTTPException(
                        detail=f"未知CHOICE: {name.lower()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                config = cls.Config()

                if config.hook_choice:
                    choices = config.hook_choice(name, choices, auth_user)

                for choice in choices:
                    schema = ChoiceModel.from_obj(choice)
                    response_model.data.append(schema)

                response_model.update()

            return response_model

        def add_api_route():
            cls.router.add_api_route(
                cls.prefix() + "/choice/{name:str}",
                endpoint=choice,
                methods=["GET"],
                response_model=ListModel,
                summary="CHOICE列表",
            )

        add_api_route()

    @classmethod
    def initialize_foreignkey(cls):
        async def foreignkey(request: Request, name: str):
            """
            ForeignKey列表
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            response_model = ListModel()

            with Session() as session, session.begin():
                session.add(auth_user)

                foreignkey = None

                for c in cls.model.__table__.c:
                    if c.name.replace("_id", "").lower() != name.lower():
                        continue

                    if not c.foreign_keys:
                        continue

                    foreignkey = (list(c.foreign_keys)[0]).column

                    break

                if foreignkey is None:
                    raise HTTPException(
                        detail=f"未知ForeignKey: {name.lower()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                foreignkey_model = None

                for model in Base.__subclasses__():
                    if model.__table__.name == foreignkey.table.name:
                        foreignkey_model = model

                        break

                config = cls.Config()

                if config.hook_foreignkey:
                    stmt, schema_model = config.hook_foreignkey(
                        name, foreignkey_model, auth_user
                    )
                else:
                    stmt = sql.select(foreignkey_model).order_by(foreignkey_model.id)
                    schema_model = NameModel

                for obj in session.execute(stmt).scalars():
                    schema = schema_model.from_obj(obj)
                    response_model.data.append(schema)

                response_model.update()

            return response_model

        def add_api_route():
            cls.router.add_api_route(
                cls.prefix() + "/foreignkey/{name:str}",
                endpoint=foreignkey,
                methods=["GET"],
                response_model=ListModel,
                summary="ForeignKey列表",
            )

        add_api_route()

    @classmethod
    def initialize_relationship(
        cls, relationship_model: Base.__class__, table: Table | Base.__class__
    ):
        async def relationship_exists(
            request: Request,
            id: int,
            page: int = 1,
            limit: int = 10,
            kwargs: str = None,
        ):
            """
            已选关联对象分页列表
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            input_model = PaginationArgsModel(page=page, limit=limit)
            response_model = PaginationModel()

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = session.get(cls.model, id)

                if not obj:
                    raise HTTPException(
                        detail=f"未知{cls.description()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                if isinstance(table, Base.__class__):
                    stmt = sql.select(table)
                else:
                    stmt = sql.select(relationship_model)

                config = cls.Config()

                if config.hook_relationship_exists_queryset:
                    stmt = config.hook_relationship_exists_queryset(
                        session,
                        stmt,
                        relationship_model,
                        table,
                        auth_user,
                        request,
                        kwargs,
                    )
                else:
                    column, relationship_column = cls.get_relationship_columns(
                        relationship_model, table
                    )

                    if column is not None and relationship_column is not None:
                        if isinstance(table, Base.__class__):
                            stmt = stmt.where(column == obj.id)
                        else:
                            stmt = stmt.where(
                                relationship_model.id.in_(
                                    sql.select(relationship_column).where(
                                        column == obj.id
                                    )
                                )
                            )
                    else:
                        stmt = stmt.where(sql.false())

                if not stmt._order_by_clauses:
                    if isinstance(table, Base.__class__):
                        stmt = stmt.order_by(table.id)
                    else:
                        stmt = stmt.order_by(relationship_model.id)

                if isinstance(table, Base.__class__):
                    schema_model = cls.get_schema_model("gets", table)
                else:
                    schema_model = cls.get_schema_model("gets", relationship_model)

                if schema_model:
                    response_model.init(session, stmt, input_model._limit())

                    for _obj in input_model.query(session, stmt).scalars():
                        schema = schema_model.from_obj(_obj)
                        schema.update(_obj)

                        if config.hook_relationship_schema:
                            config.hook_relationship_schema(session, _obj, schema)

                        response_model.data.append(schema)

            return response_model

        async def relationship_remaining(
            request: Request,
            id: int,
            page: int = 1,
            limit: int = 10,
            kwargs: str = None,
        ):
            """
            未选关联对象分页列表
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            input_model = PaginationArgsModel(page=page, limit=limit)
            response_model = PaginationModel()

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = session.get(cls.model, id)

                if not obj:
                    raise HTTPException(
                        detail=f"未知{cls.description()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                stmt = sql.select(relationship_model)

                config = cls.Config()

                if config.hook_relationship_remaining_queryset:
                    stmt = config.hook_relationship_remaining_queryset(
                        session,
                        stmt,
                        relationship_model,
                        table,
                        auth_user,
                        request,
                        kwargs,
                    )
                else:
                    column, relationship_column = cls.get_relationship_columns(
                        relationship_model, table
                    )

                    if column is not None and relationship_column is not None:
                        stmt = stmt.where(
                            relationship_model.id.not_in(
                                sql.select(relationship_column).where(column == obj.id)
                            )
                        )
                    else:
                        stmt = stmt.where(sql.false())

                if not stmt._order_by_clauses:
                    stmt = stmt.order_by(relationship_model.id)

                schema_model = cls.get_schema_model("gets", relationship_model)

                if schema_model:
                    response_model.init(session, stmt, input_model._limit())

                    for _obj in input_model.query(session, stmt).scalars():
                        schema = schema_model.from_obj(_obj)
                        schema.update(_obj)

                        if config.hook_relationship_schema:
                            config.hook_relationship_schema(session, _obj, schema)

                        response_model.data.append(schema)

            return response_model

        async def relationship_add(request: Request, id: int, ids: list[int]):
            """
            新增关联对象
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = session.get(cls.model, id)

                if not obj:
                    raise HTTPException(
                        detail=f"未知{cls.description()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                column, relationship_column = cls.get_relationship_columns(
                    relationship_model, table
                )

                if column is not None and relationship_column is not None:
                    attribute = cls.get_relationship_attribute(
                        obj, relationship_model, table
                    )

                    if attribute is not None:
                        config = cls.Config()

                        if isinstance(table, Base.__class__):
                            stmt = sql.select(relationship_model).where(
                                relationship_model.id.in_(ids),
                                relationship_model.id.not_in(
                                    sql.select(relationship_column).where(
                                        column == obj.id
                                    )
                                ),
                            )

                            for relationship_obj in session.execute(stmt).scalars():
                                if config.hook_relationship_obj:
                                    new_obj = config.hook_relationship_obj(
                                        session, obj, relationship_obj
                                    )
                                else:
                                    new_obj = table()

                                    setattr(new_obj, column.name, obj.id)
                                    setattr(
                                        new_obj,
                                        relationship_column.name,
                                        relationship_obj.id,
                                    )

                                attribute.append(new_obj)

                                if config.hook_relationship_add:
                                    config.hook_relationship_add(
                                        session, new_obj, table
                                    )
                        else:
                            stmt = sql.select(relationship_model).where(
                                relationship_model.id.in_(ids)
                            )

                            for _obj in session.execute(stmt).scalars():
                                attribute.append(_obj)

                                if config.hook_relationship_add:
                                    config.hook_relationship_add(session, _obj, table)

            return SuccessModel()

        async def relationship_delete(request: Request, id: int, ids: list[int]):
            """
            删除关联对象
            """

            auth_user = authentication(request.user, cls.scope, cls.superadmin)

            with Session() as session, session.begin():
                session.add(auth_user)

                obj = session.get(cls.model, id)

                if not obj:
                    raise HTTPException(
                        detail=f"未知{cls.description()}",
                        status_code=HTTP_400_BAD_REQUEST,
                    )

                config = cls.Config()

                column, relationship_column = cls.get_relationship_columns(
                    relationship_model, table
                )

                if column is not None and relationship_column is not None:
                    if config.hook_relationship_delete:
                        stmt = sql.sql(table).where(
                            column == obj.id, relationship_column.in_(ids)
                        )

                        for _obj in session.execute(stmt).scalars():
                            config.hook_relationship_delete(session, _obj, table)
                            session.delete(_obj)
                    else:
                        session.execute(
                            sql.delete(table).where(
                                column == obj.id, relationship_column.in_(ids)
                            )
                        )

            return SuccessModel()

        def add_api_route():
            relationship_model_name = relationship_model.__name__
            relationship_model_description = (
                (relationship_model.__doc__ or "").strip().splitlines()[0]
            )

            cls.router.add_api_route(
                cls.prefix()
                + "/{id:int}/relationship/exists"
                + f"/{relationship_model_name.lower()}",
                endpoint=relationship_exists,
                methods=["GET"],
                response_model=PaginationModel,
                summary=f"已选关联对象{relationship_model_description}分页列表",
            )

            cls.router.add_api_route(
                cls.prefix()
                + "/{id:int}/relationship/remaining"
                + f"/{relationship_model_name.lower()}",
                endpoint=relationship_remaining,
                methods=["GET"],
                response_model=PaginationModel,
                summary=f"未选关联对象{relationship_model_description}分页列表",
            )

            cls.router.add_api_route(
                cls.prefix()
                + "/{id:int}/relationship/add"
                + f"/{relationship_model_name.lower()}",
                endpoint=relationship_add,
                methods=["POST"],
                response_model=SuccessModel,
                summary=f"新增关联对象{relationship_model_description}",
            )

            cls.router.add_api_route(
                cls.prefix()
                + "/{id:int}/relationship/delete"
                + f"/{relationship_model_name.lower()}",
                endpoint=relationship_delete,
                methods=["DELETE"],
                response_model=SuccessModel,
                summary=f"删除关联对象{relationship_model_description}",
            )

        add_api_route()

    class Config:
        gets: bool = True
        get: bool = True

        create: bool = True
        update: bool = True

        delete: bool = False
        delete_one: bool = False

        # --------------------------------------------------
        #
        #                     CRUD HOOK
        #
        # --------------------------------------------------

        # 对象分页列表查询条件HOOK
        #   参数  : session, stmt, auth_user, request, kwargs
        #   返回  : stmt
        hook_gets_queryset: Callable | None = None

        # 对象分页列表模型扩展HOOK
        #   参数  : session, obj, schema
        #   返回  : 无
        hook_gets_schema: Callable | None = None

        # 获取对象信息检查HOOK
        #   参数  : obj
        #   返回  : 无
        hook_get_check: Callable | None = None

        # 获取对象信息模型扩展HOOK
        #   参数  : session, obj, schema
        #   返回  : 无
        hook_get_schema: Callable | None = None

        # 新建对象扩展HOOK
        #   参数  : session, obj, auth_user, schema
        #   返回  : 无
        hook_create_obj: Callable | None = None

        # 更新对象检查HOOK
        #   参数  : obj
        #   返回  : 无
        hook_update_check: Callable | None = None

        # 更新对象扩展HOOK
        #   参数  : session, obj, auth_user, schema
        #   返回  : 无
        hook_update_obj: Callable | None = None

        # 删除对象扩展HOOK
        #   参数  : obj
        #   返回  : 无
        hook_delete: Callable | None = None

        # --------------------------------------------------
        #
        #                    CHOICES HOOK
        #
        # --------------------------------------------------

        # CHOICE列表HOOK
        #   参数  : name, choices, auth_user
        #   返回  : choices
        hook_choice: Callable | None = None

        # --------------------------------------------------
        #
        #                   FOREIGNKEY HOOK
        #
        # --------------------------------------------------

        # ForeignKey列表HOOK
        #   参数  : name, foreignkey_model, auth_user
        #   返回  : stmt, schema_model
        hook_foreignkey: Callable | None = None

        # --------------------------------------------------
        #
        #                   RELATIONSHIP HOOK
        #
        # --------------------------------------------------

        # 已选关联对象分页列表查询条件HOOK
        #   参数  : session, stmt, relationship_model, table, auth_user, request, kwargs
        #   返回  : stmt
        hook_relationship_exists_queryset: Callable | None = None

        # 未选关联对象分页列表查询条件HOOK
        #   参数  : session, stmt, relationship_model, table, auth_user, request, kwargs
        #   返回  : stmt
        hook_relationship_remaining_queryset: Callable | None = None

        # 关联对象分页列表模型扩展HOOK
        #   参数  : session, obj, schema
        #   返回  : 无
        hook_relationship_schema: Callable | None = None

        # 新增关联对象创建HOOK
        #   参数  : session, obj, relationship_obj
        #   返回  : new_obj
        hook_relationship_obj: Callable | None = None

        # 新增关联对象扩展HOOK
        #   参数  : session, obj, table
        #   返回  : 无
        hook_relationship_add: Callable | None = None

        # 删除关联对象扩展HOOK
        #   参数  : session, obj, table
        #   返回  : 无
        hook_relationship_delete: Callable | None = None
